/*******************************************************************************
 * Copyright (c) 2006, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.handlers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.ISourceProvider;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.expressions.AndExpression;

/**
 * A handler service which delegates almost all responsibility to the parent
 * service. It is only responsible for disposing of locally activated handlers
 * when it is disposed.
 * <p>
 * This class is not intended for use outside of the
 * <code>org.eclipse.ui.workbench</code> plug-in.
 * </p>
 */
public class SlaveHandlerService implements IHandlerService {

	/**
	 * The default expression to use when
	 * {@link #activateHandler(String, IHandler)} is called. Handlers
	 * contributed using that method will only be active when this service is
	 * active. However, this expression will be used for conflict resolution.
	 */
	protected final Expression defaultExpression;

	/**
	 * A collection of source providers. The listeners are not
	 * activated/deactivated, but they will be removed when this service is
	 * disposed.
	 */
	private Collection fSourceProviders = new ArrayList();

	/**
	 * A map of the local activation to the parent activations. If this service
	 * is inactive, then all parent activations are <code>null</code>.
	 * Otherwise, they point to the corresponding activation in the parent
	 * service.
	 */
	protected final Map localActivationsToParentActivations = new HashMap();

	/**
	 * The parent handler service to which all requests are ultimately routed.
	 * This value is never <code>null</code>.
	 */
	protected final IHandlerService parent;

	/**
	 * The activations registered with the parent handler service. This value is
	 * never <code>null</code>.
	 */
	protected final Set parentActivations = new HashSet();

	/**
	 * Constructs a new instance.
	 * 
	 * @param parentHandlerService
	 *            The parent handler service for this slave; must not be
	 *            <code>null</code>.
	 * @param defaultExpression
	 *            The default expression to use if none is provided. This is
	 *            primarily used for conflict resolution. This value may be
	 *            <code>null</code>.
	 */
	public SlaveHandlerService(final IHandlerService parentHandlerService,
			final Expression defaultExpression) {
		if (parentHandlerService == null) {
			throw new NullPointerException(
					"The parent handler service cannot be null"); //$NON-NLS-1$
		}

		this.defaultExpression = defaultExpression;
		this.parent = parentHandlerService;
	}

	public final IHandlerActivation activateHandler(
			final IHandlerActivation childActivation) {
		final String commandId = childActivation.getCommandId();
		final IHandler handler = childActivation.getHandler();
		final Expression childExpression = childActivation.getExpression();
		Expression expression = defaultExpression;
		if (childExpression != null && defaultExpression != null) {
			final AndExpression andExpression = new AndExpression();
			andExpression.add(childExpression);
			andExpression.add(defaultExpression);
			expression = andExpression;
		} else if (childExpression != null) {
			expression = childExpression;
		}
		final int depth = childActivation.getDepth() + 1;
		final IHandlerActivation localActivation = new HandlerActivation(
				commandId, handler, expression, depth, this);

		return doActivation(localActivation);
	}

	public final IHandlerActivation activateHandler(final String commandId,
			final IHandler handler) {
		final IHandlerActivation localActivation = new HandlerActivation(
				commandId, handler, defaultExpression,
				IHandlerActivation.ROOT_DEPTH, this);
		return doActivation(localActivation);
	}

	public final IHandlerActivation activateHandler(final String commandId,
			final IHandler handler, final Expression expression) {
		return activateHandler(commandId, handler, expression, false);
	}

	public final IHandlerActivation activateHandler(final String commandId,
			final IHandler handler, final Expression expression,
			final boolean global) {
		if (global) {
			final IHandlerActivation activation = parent.activateHandler(
					commandId, handler, expression, global);
			parentActivations.add(activation);
			return activation;
		}

		Expression aExpression = defaultExpression;
		if (expression != null && defaultExpression != null) {
			final AndExpression andExpression = new AndExpression();
			andExpression.add(expression);
			andExpression.add(defaultExpression);
			aExpression = andExpression;
		} else if (expression != null) {
			aExpression = expression;
		}
		final IHandlerActivation localActivation = new HandlerActivation(
				commandId, handler, aExpression,
				IHandlerActivation.ROOT_DEPTH, this);
		return doActivation(localActivation);
	}

	public final IHandlerActivation activateHandler(final String commandId,
			final IHandler handler, final Expression expression,
			final int sourcePriorities) {
		return activateHandler(commandId, handler, expression);
	}

	public final void addSourceProvider(final ISourceProvider provider) {
		if (!fSourceProviders.contains(provider)) {
			fSourceProviders.add(provider);
		}
		parent.addSourceProvider(provider);
	}

	public final ExecutionEvent createExecutionEvent(final Command command,
			final Event event) {
		return parent.createExecutionEvent(command, event);
	}

	public final ExecutionEvent createExecutionEvent(
			final ParameterizedCommand command, final Event event) {
		return parent.createExecutionEvent(command, event);
	}

	public final void deactivateHandler(final IHandlerActivation activation) {
		final IHandlerActivation parentActivation;
		if (localActivationsToParentActivations.containsKey(activation)) {
			parentActivation = (IHandlerActivation) localActivationsToParentActivations
					.remove(activation);
		} else {
			parentActivation = activation;
		}

		if (parentActivation != null) {
			parent.deactivateHandler(parentActivation);
			parentActivations.remove(parentActivation);
		}
	}

	public final void deactivateHandlers(final Collection activations) {
		Object[] array = activations.toArray();
		for (int i = 0; i < array.length; i++) {
			deactivateHandler((IHandlerActivation) array[i]);
			array[i] = null;
		}
	}

	public final void dispose() {
		parent.deactivateHandlers(parentActivations);
		parentActivations.clear();
		localActivationsToParentActivations.clear();

		// Remove any "resource", like listeners, that were associated
		// with this service.
		if (!fSourceProviders.isEmpty()) {
			Object[] array = fSourceProviders.toArray();
			for (int i = 0; i < array.length; i++) {
				removeSourceProvider((ISourceProvider) array[i]);
			}
			fSourceProviders.clear();
		}
	}

	protected IHandlerActivation doActivation(
			final IHandlerActivation localActivation) {
		final IHandlerActivation parentActivation;
		parentActivation = parent.activateHandler(localActivation);
		parentActivations.add(parentActivation);
		localActivationsToParentActivations.put(localActivation,
				parentActivation);
		return localActivation;
	}

	public final Object executeCommand(final ParameterizedCommand command,
			final Event event) throws ExecutionException, NotDefinedException,
			NotEnabledException, NotHandledException {
		return parent.executeCommand(command, event);
	}

	public final Object executeCommand(final String commandId, final Event event)
			throws ExecutionException, NotDefinedException,
			NotEnabledException, NotHandledException {
		return parent.executeCommand(commandId, event);
	}

	public final IEvaluationContext getCurrentState() {
		return parent.getCurrentState();
	}

	public final void readRegistry() {
		parent.readRegistry();
	}

	public final void removeSourceProvider(final ISourceProvider provider) {
		fSourceProviders.remove(provider);
		parent.removeSourceProvider(provider);
	}

	public final void setHelpContextId(final IHandler handler,
			final String helpContextId) {
		parent.setHelpContextId(handler, helpContextId);
	}

	Expression getDefaultExpression() {
		return defaultExpression;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.handlers.IHandlerService#createContextSnapshot(boolean)
	 */
	public IEvaluationContext createContextSnapshot(boolean includeSelection) {
		return parent.createContextSnapshot(includeSelection);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.handlers.IHandlerService#executeCommandInContext(org.eclipse.core.commands.ParameterizedCommand, org.eclipse.swt.widgets.Event, org.eclipse.core.expressions.IEvaluationContext)
	 */
	public Object executeCommandInContext(ParameterizedCommand command,
			Event event, IEvaluationContext context) throws ExecutionException,
			NotDefinedException, NotEnabledException, NotHandledException {
		return parent.executeCommandInContext(command, event, context);
	}
}

